﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheretanceAndPoly
{

    class Person
    {

        // Default variables are private
        string Name;

        // public variables
        public string Sex;

        // Protected variables are accesable with child classes
        protected string Address;
        protected int Age;

        public Person()
        {
            Name = "";
            Age = 0;
            Sex = "Male";
            Address = "";
        }

        public Person(string NameValue, int AgeValue)
        {
            Name = NameValue;
            Age = AgeValue;
        }

       public void PrintData()
        {
            Console.WriteLine("Name: {0} Age {1}", Name, Age);
        }

       public void PrintAge()
       {
           Console.WriteLine("Age: ", Age);
       }

       // Making a virtual method, this way we can override the method in the child classes
       // This is not the same as method hiding because with method hiding we hide the origonal method
       // and here we override the method
       public virtual void Up()
       {
           Console.WriteLine("Parent class");
       }
    }

    class Student : Person
    {
        // Default variables are private
        string Major;

        // The parent constructor is always called before the child constructor
        // Since the parent class Person has two constructors we specify which constructor we
        // want to call, in this case it is the one with the parameters.
        // By default the constructor without parameters is called
        public Student() : base("StudentName", 10)
        {
            Sex = "Male";
            Major = "TI";
            Address = "Address";
        }

        // We can access the Address variabele because the Student class is a sub class of the Person class 
        public void PrintStudentData()
        {
            Console.WriteLine("Major: {0} Address: {1}", Major, Address);
        }

        // This method will hide the the orginal parent class method, it will hide the origonal PrintAge
        // method. This is called method hiding, it is not the same as overwriting
        // We use the new keyword for method hiding
        public new void PrintAge()
        {
            Console.WriteLine("Age Student: ", Age);
        }

        // Here we use the override ketyword so that we say that on runtime the Up method will be overridden
        // The Up method of the parent class will be overridden because it is marked with the virtual keyword
        // This is dynamic polymorphism, it allows us to use child class methods using a base/parrent clase variable at runtime
        // because this is dynamic polymorphism.
        public override void Up()
        {
            Console.WriteLine("Child class");
        }
    }

    class Program
    {

        // A method to show how the scope works, it is static because this way we don't have the make it member function
        // of the object class (so we don't have to create a object). Also the Method1 function will only be made once (in case there will be more objects of the program class
        // if so then it will be shared by all the created objects of the Program class)
        static void Method1()
        {
            // Here we create a object
            Person Object1 = new Person();

            Console.WriteLine("In Method1 method");
            // Do some stuff with the object


            // At the end of the function just before the function returns the object will
            // become out of scope. When this happens the object will be marked for the garbage collector.
            // Since the object has no reference to it anymore (the reference varaible Object1) will be deleted 
            // when the function returns

            Console.WriteLine("Returning from method deleting Object1 object variable and thereby deleting any references to the object and marking it for garbage collection");
        }

        static void Main(string[] args)
        {
            // Create object, allocate memory 
            Person Object1 = new Person("Roy", 28);
            Student Object2 = new Student();

            // Here we create a variable with enoug space to store the object
            // reference (note not the actual object). This space is allocated on the stack
            Person Object4;

            // Here we create the object and allocate the actual object on the heap
            // All objects are stored on the heap (dynamic memory)
            // The variabele Object4 that is stored on the stack now references the object on the heap
            // It points to to address of the actual object on the heap, so it contains the memory address 
            // of the object on the heap
            Object4 = new Person();

            Object1.PrintData();

            Object1.Sex = "Male";

            Object2.PrintStudentData();

            Object2.PrintAge();
            Object1.PrintAge();

            // Here specify that we want to use the person class PrintAge method instead of the class method
            ((Person)Object2).PrintAge();

            // A parent class can have a child class (not the other way around)
            // We assign a child object to the parent class variable
            Person Object3 = new Student();

            // Create a person array, it can contain parent and child objects
            Person[] Persons = new Person[4];

            Persons[0] = new Person();
            Persons[1] = new Student();
            Persons[2] = new Person();
            Persons[3] = new Student();

            for (int i = 0; i < 4; i++)
            {
                // Here we use dynamic polymorpsim to use the right method on runtime
                // Persons is a Person (parent) class objects array, it contains parrent and child objects
                // On runtime the right parent or child method Up will be called because the Up method will
                // be overridden, despite that the array contains parent class variables
                // Dynamic polymorphism allows us to use a base class variable and calling child methods by overriding the
                // parent methods
                Persons[i].Up();
            }

            Method1();

            // Program will get out of scope here now the created objects will be marked for 
            // the garbage collector, the garbage collector will clean up the objects this may happen
            // right away or in a little time
        }
    }
}
